﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace MidasGtsExporter.ABAQUS
{
	public class AbaqusExporter
	{
		#region private fields

		private SortedList<int, Node> _nodeList;

		private SortedList<int, Elem> _elem3DList;
		private SortedList<int, Elem> _elem2DList;
		private SortedList<int, Elem> _elem1DList;

		private SortedList<int, List<int>> _nodeGroups;
		private SortedList<int, List<int>> _elem3DGroups;
		private SortedList<int, List<int>> _elem2DGroups;
		private SortedList<int, List<int>> _elem1DGroups;

		private Dictionary<int, string> _nodeGroupNames;
		private Dictionary<int, string> _elem3DGroupNames;
		private Dictionary<int, string> _elem2DGroupNames;
		private Dictionary<int, string> _elem1DGroupNames;

		#endregion

		#region public properties

		public string InputFilePath { get; set; }
		public string OutputFolderPath { get; set; }
		public string OutputFileName { get; set; } = "AbaqusMesh"; // 不包含扩展名
		public LsDynaOption.Option Option { get; set; }

		public string OutputFileNameWithExtension => $"{OutputFileName}.inp";

		public string IncludeFolderName => $"{OutputFileName}_Include";
		public string IncludeFolderPath => Path.Combine(OutputFolderPath, IncludeFolderName);

		#endregion

		public void DoExport(BackgroundWorker backgroundWorker)
		{
			Debug.Assert(IO.IsFileExist(InputFilePath));

			//
			// Parse Midas GTS FPN file
			//
			backgroundWorker.ReportProgress(10, "解析FPN文件内容 ......");

			var gtsReader = new GtsFpnDataReader();
			gtsReader.ReadData(InputFilePath);

			_nodeList = gtsReader.NodeList;
			_elem3DList = gtsReader.Elem3DList;
			_elem2DList = gtsReader.Elem2DList;
			_elem1DList = gtsReader.Elem1DList;
			Debug.Assert(_nodeList != null && _elem3DList != null
			             && _elem2DList != null && _elem1DList != null);
			if (_nodeList.Count <= 0 || (_elem3DList.Count <= 0) && (_elem2DList.Count <= 0))
			{
				backgroundWorker.ReportProgress(100, "*** FPN文件中无有效节点和单元数据.");
				return;
			}

			//
			// Convert mesh data
			//
			backgroundWorker.ReportProgress(30, "转换网格数据 ......");

			_elem3DGroups = gtsReader.Elem3DGroups;
			_elem2DGroups = gtsReader.Elem2DGroups;
			_elem1DGroups = gtsReader.Elem1DGroups;
			_nodeGroups = gtsReader.NodeGroups;
			Debug.Assert(_elem3DGroups != null && _elem2DGroups != null && _elem1DGroups != null && _nodeGroups != null);

			var elemGroupNames = gtsReader.ElemGroupNames;
			var nodeGroupNames = gtsReader.NodeGroupNames;
			_nodeGroupNames = gtsReader.NodeGroups
				.ToDictionary(kv => kv.Key, kv => $"nset_{nodeGroupNames[kv.Key]}");
			_elem3DGroupNames = _elem3DGroups
				.ToDictionary(kv => kv.Key, kv => $"eset_{elemGroupNames[kv.Key]}");
			_elem2DGroupNames = _elem2DGroups
				.ToDictionary(kv => kv.Key, kv => $"eset_{elemGroupNames[kv.Key]}");
			_elem1DGroupNames = _elem1DGroups
				.ToDictionary(kv => kv.Key, kv => $"eset_{elemGroupNames[kv.Key]}");

			//
			// Check node and element data
			//
			/*
			backgroundWorker.ReportProgress(50, "检查节点和单元数据 ......");
			{

			}
			*/

			//
			// Output
			//
			backgroundWorker.ReportProgress(70, "输出Abaqus网格数据 ......");

			CreateAbaqusInpFile(backgroundWorker);

			//
			// Output statistical information
			//
			{
				backgroundWorker.ReportProgress(90, " ****** 输出数据统计 ****** ");

				//
				var nodeCount = _nodeList.Count;
				var elem3DCount = _elem3DGroups?.Sum(g => g.Value.Count) ?? 0;
				var elem2DCount = _elem2DGroups?.Sum(g => g.Value.Count) ?? 0;
				var elem1DCount = _elem1DGroups?.Sum(g => g.Value.Count) ?? 0;
				var elemCount = elem3DCount + elem2DCount + elem1DCount;

				var newLine = Environment.NewLine;
				backgroundWorker.ReportProgress(91, $"节点数量：{nodeCount}");
				if (_nodeGroups != null && _nodeGroups.Count > 0)
				{
					backgroundWorker.ReportProgress(94,
						string.Join(newLine, _nodeGroups.Select(g => $"    {_nodeGroupNames[g.Key]}: {g.Value.Count}")));
				}

				backgroundWorker.ReportProgress(92, $"单元数量：{elemCount}");
				if (_elem3DGroups != null && _elem3DGroups.Count > 0)
				{
					backgroundWorker.ReportProgress(93, $"3D单元数量：{elem3DCount}");
					backgroundWorker.ReportProgress(94,
						string.Join(newLine, _elem3DGroups.Select(g => $"    {_elem3DGroupNames[g.Key]}: {g.Value.Count}")));
				}
				if (_elem2DGroups != null && _elem2DGroups.Count > 0)
				{
					backgroundWorker.ReportProgress(95, $"2D单元数量：{elem2DCount}");
					backgroundWorker.ReportProgress(96,
						string.Join(newLine, _elem2DGroups.Select(g => $"    {_elem2DGroupNames[g.Key]}: {g.Value.Count}")));
				}
				if (_elem1DGroups != null && _elem1DGroups.Count > 0)
				{
					backgroundWorker.ReportProgress(95, $"1D单元数量：{elem2DCount}");
					backgroundWorker.ReportProgress(96,
						string.Join(newLine, _elem1DGroups.Select(g => $"    {_elem1DGroupNames[g.Key]}: {g.Value.Count}")));
				}
			}
		}

		private void CreateAbaqusInpFile(BackgroundWorker backgroundWorker)
		{
			var content = new List<string>
			{
				"** ",
				$"** Abaqus Mesh generated by MidasGtsExporter v{AppInfo.Version} on {DateTime.Now}",
				"** ",
				"*Heading",
				"*Part, name=Part_MidasGts",
			};

			//
			// node definition
			//
			content.Add("*Node, nset=nset_all_node");
			content.AddRange(_nodeList.Select(kv =>
			{
				var node = kv.Value;
				return $"{node.Id},{node.X:E6},{node.Y:E6},{node.Z:E6}";
			}));

			//
			// element definition
			//
			string GenElemLine(Elem elem, IEnumerable<int> idIndex)
			{
				return $"{elem.Id}," + string.Join(",", idIndex.Select(i => $"{elem.NodeIds[i - 1]}"));
			}

			//
			// 3D elements
			//
			if (_elem3DList != null && _elem3DList.Count > 0)
			{
				content.Add($"*Element, type=C3D8R, elset=eset_3D");
				var elems = _elem3DList;

				var sourceNodeCount = 8;
				var nodeIdIndex = new[] { 1, 2, 3, 4, 5, 6, 7, 8 };
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(kv.Value, nodeIdIndex)));

				sourceNodeCount = 6;
				nodeIdIndex = new[] { 1, 2, 3, 3, 4, 5, 6, 6 };
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(kv.Value, nodeIdIndex)));

				sourceNodeCount = 5;
				nodeIdIndex = new[] { 1, 2, 3, 4, 5, 5, 5, 5 };
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(kv.Value, nodeIdIndex)));

				sourceNodeCount = 4;
				nodeIdIndex = new[] { 1, 2, 3, 3, 4, 4, 4, 4 };
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(kv.Value, nodeIdIndex)));
			}

			//
			// 2D elements
			//
			if (_elem2DList != null && _elem2DList.Count > 0)
			{
				content.Add($"*Element, type=S4R, elset=eset_2D");
				var elems = _elem2DList;

				var sourceNodeCount = 4;
				var nodeIdIndex = new[] { 1, 2, 3, 4 };
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(kv.Value, nodeIdIndex)));

				sourceNodeCount = 3;
				nodeIdIndex = new[] { 1, 2, 3, 3 };
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(kv.Value, nodeIdIndex)));
			}

			//
			// 1D elements
			//
			if (_elem1DList != null && _elem1DList.Count > 0)
			{
				content.Add($"*Element, type=B31, elset=eset_1D");
				var elems = _elem1DList;

				var sourceNodeCount = 2;
				var nodeIdIndex = new[] { 1, 2 };
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(kv.Value, nodeIdIndex)));
			}

			//
			// node group
			//
			if (_nodeGroups != null)
			{
				foreach (var group in _nodeGroups)
				{
					var groupName = _nodeGroupNames[group.Key];
					content.AddRange(new[]
					{
						$"*Nset, nset={groupName}"
					});

					const int idCountPerLine = 16;
					var idLines = group.Value
						.GetSlices(idCountPerLine)
						.Select(ids => string.Join(",", ids.Select(id => $"{id}")));
					content.AddRange(idLines);
				}
			}

			//
			// 3D element group
			//
			if (_elem3DGroups != null)
			{
				foreach (var group in _elem3DGroups)
				{
					var groupName = _elem3DGroupNames[group.Key];
					content.AddRange(new[]
					{
						$"*Elset, elset={groupName}"
					});

					const int idCountPerLine = 16;
					var idLines = group.Value
						.GetSlices(idCountPerLine)
						.Select(ids => string.Join(",", ids.Select(id => $"{id}")));
					content.AddRange(idLines);
				}
			}

			//
			// 2D element group
			//
			if (_elem2DGroups != null)
			{
				foreach (var group in _elem2DGroups)
				{
					var groupName = _elem2DGroupNames[group.Key];
					content.AddRange(new[]
					{
						$"*Elset, elset={groupName}"
					});

					const int idCountPerLine = 16;
					var idLines = group.Value
						.GetSlices(idCountPerLine)
						.Select(ids => string.Join(",", ids.Select(id => $"{id}")));
					content.AddRange(idLines);
				}
			}

			//
			// 3D element group
			//
			if (_elem1DGroups != null)
			{
				foreach (var group in _elem1DGroups)
				{
					var groupName = _elem1DGroupNames[group.Key];
					content.AddRange(new[]
					{
						$"*Elset, elset={groupName}"
					});

					const int idCountPerLine = 16;
					var idLines = group.Value
						.GetSlices(idCountPerLine)
						.Select(ids => string.Join(",", ids.Select(id => $"{id}")));
					content.AddRange(idLines);
				}
			}

			//
			content.Add("*End Part");

			//
			// Write to file
			//
			var filePath = Path.Combine(OutputFolderPath, OutputFileNameWithExtension);
			backgroundWorker.ReportProgress(90, $"Creating file: {filePath}");
			File.WriteAllLines(filePath, content);
		}
	}
}